﻿using Microsoft.TeamFoundation.Build.Client;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace BuildExplorer
{
    public class RichTextNode
    {
        RichTextBox textBox;
        int updateCount = 0;

        int highLightIndex = -1;
        string highLighted = "";

        public RichTextNode(RichTextBox r)
        {
            textBox = r;
        }

        void BeginUpdate()
        {
            if (updateCount++ == 0)
            {
                textBox.BeginUpdate();
            }
        }

        void EndUpdate()
        {
            if (--updateCount == 0)
            {
                textBox.EndUpdate();
            }
        }

        public void Update(IBuildInformationNode node, string text, Action<string> afterUpdate)
        {
            BeginUpdate();
            textBox.Clear();

            if (node != null)
            {
                IBuildInformationNode containingAgentScope = node.GetContainingAgentScope();
                if (containingAgentScope != null)
                {
                    textBox.AppendText(containingAgentScope.GetAgentScopeDescription() + "\n");
                }

                AddFields(node.GetNodeText(), node.Fields);

                if (node.ActivityPropertiesNode() != null)
                {
                    AddFields("Activity Properties", node.ActivityPropertiesNode().Fields);
                }

                if (node.BuildMessages().Any())
                {
                    AddLines("Build Messages", node.BuildMessages()
                        .Select(n => string.Format("{0}  {1}", Util.FormatValue(n.Fields["Timestamp"]), n.Fields["Message"])));

                }
            }

            Highlight(text);

            if (afterUpdate != null)
            {
                afterUpdate(text);
            }

            EndUpdate();
        }

        public void Highlight(string text)
        {
            BeginUpdate();
            highLightIndex = -1;
            highLighted = text;

            textBox.SelectionStart = 0;
            textBox.SelectionLength = textBox.Text.Length;
            textBox.SelectionBackColor = textBox.BackColor;

            int highlights = 0;

            if (!string.IsNullOrEmpty(text))
            {
                int index = 0;

                string boxText = textBox.Text;
                while ((index = boxText.IndexOf(text, index, StringComparison.OrdinalIgnoreCase)) != -1 && (highlights++) < 1000)
                {
                    Highlight(index, text.Length, Color.Yellow);
                    index += text.Length;
                }
            }

            // reset selection, otherwise selecting into the RichTextBox will skip to the bottom of it
            textBox.Select(0, 0);
            EndUpdate();
        }

        // Returns: false - not found, true - found (and highlighted+selected)
        public bool SearchNext(string text)
        {
            return MoveSearchHighlight(textBox.Text.IndexOf(text, textBox.SelectionStart + textBox.SelectionLength, StringComparison.CurrentCultureIgnoreCase));
        }

        // Returns: false - not found, true - found (and highlighted+selected)
        public bool SearchPrev(string text)
        {
            if (textBox.SelectionStart == 0)
            {
                return false;
            }

            return MoveSearchHighlight(textBox.Text.LastIndexOf(text, textBox.SelectionStart - 1, StringComparison.CurrentCultureIgnoreCase));
        }

        // Returns: false - not found, true - found (and highlighted+selected)
        public bool SearchPrevFromEnd(string text)
        {
            return MoveSearchHighlight(textBox.Text.LastIndexOf(text, textBox.TextLength, StringComparison.CurrentCultureIgnoreCase));
        }

        private bool MoveSearchHighlight(int newHighLightIndex)
        {
            if (newHighLightIndex == -1)
            {
                return false;
            }

            BeginUpdate();
            if (highLightIndex != -1)
            {
                Highlight(highLightIndex, highLighted.Length, Color.Yellow);
            }

            Highlight(newHighLightIndex, highLighted.Length, Color.Orange);
            highLightIndex = newHighLightIndex;

            ScrollToIndexIfNeeded(newHighLightIndex);

            EndUpdate();
            return true;
        }

        private void ScrollToIndexIfNeeded(int index)
        {
            Point p = textBox.GetPositionFromCharIndex(index);
            if (p.Y < 0)
            {
                textBox.ScrollToCaret();  // Scroll up
            }
            else
            {
                Size c = textBox.ClientSize;
                int currentLine = textBox.GetLineFromCharIndex(index);
                int nextLineChar = textBox.GetFirstCharIndexFromLine(currentLine + 1);
                Point pNext = textBox.GetPositionFromCharIndex(nextLineChar);
                if (pNext.Y > c.Height)
                {
                    textBox.ScrollToCaret();   // Next line starts below client area, so our line is partially obscured - so scroll down
                }
            }
        }

        private void Highlight(int index, int len, Color color)
        {
            textBox.Select(index, len);
            textBox.SelectionBackColor = color;
        }

        private void AddFields(string title, Dictionary<string, string> fields)
        {
            AddTitle(title);

            textBox.SetSelectionSpaceAfter(40);

            foreach (var field in fields)
            {
                textBox.SelectionFont = new Font(textBox.Font, FontStyle.Bold);
                textBox.AppendText(field.Key + ": ");

                textBox.SelectionFont = textBox.Font;

                textBox.AppendText(Util.FormatValue(field.Value) + "\n");
            }

            textBox.AppendText("\n");
        }

        private void AddLines(string title, IEnumerable<string> lines)
        {
            AddTitle(title);

            //// This does not work when log > 100KB, see http://stackoverflow.com/questions/15529829/
            //textBox.SelectionFont = new Font("Consolas", 9);  
            //textBox.SetSelectionSpaceAfter(0);

            int before = textBox.TextLength;

            textBox.AppendText(string.Join("\n", lines));
            textBox.AppendText("\n");

            textBox.SelectionStart = before;
            textBox.SelectionLength = textBox.TextLength - before;

            textBox.SelectionFont = new Font("Consolas", 9);
            textBox.SetSelectionSpaceAfter(0);
        }


        private void AddTitle(string title)
        {
            textBox.SelectionIndent = -20;
            textBox.SelectionHangingIndent = 20;
            textBox.SetSelectionSpaceAfter(120);

            textBox.SelectionFont = new Font(textBox.Font.FontFamily, 16, FontStyle.Regular);
            textBox.AppendText(title + "\n");
        }
    }
}
